import defined from '../Core/defined.js';
import DeveloperError from '../Core/DeveloperError.js';

    /**
     * A function to port GLSL shaders from GLSL ES 1.00 to GLSL ES 3.00
     *
     * This function is nowhere near comprehensive or complete. It just
     * handles some common cases.
     *
     * Note that this function requires the presence of the
     * "#define OUTPUT_DECLARATION" line that is appended
     * by ShaderSource.
     *
     * @private
     */
    function modernizeShader(source, isFragmentShader) {
        var outputDeclarationRegex = /#define OUTPUT_DECLARATION/;
        var splitSource = source.split('\n');

        if (/#version 300 es/g.test(source)) {
            return source;
        }

        var outputDeclarationLine = -1;
        var i, line;
        for (i = 0; i < splitSource.length; ++i) {
            line = splitSource[i];
            if (outputDeclarationRegex.test(line)) {
                outputDeclarationLine = i;
                break;
            }
        }

        if (outputDeclarationLine === -1) {
            throw new DeveloperError('Could not find a #define OUTPUT_DECLARATION!');
        }

        var outputVariables = [];

        for (i = 0; i < 10; i++) {
            var fragDataString = 'gl_FragData\\[' + i + '\\]';
            var newOutput = 'czm_out' + i;
            var regex = new RegExp(fragDataString, 'g');
            if (regex.test(source)) {
                setAdd(newOutput, outputVariables);
                replaceInSourceString(fragDataString, newOutput, splitSource);
                splitSource.splice(outputDeclarationLine, 0, 'layout(location = ' + i + ') out vec4 ' + newOutput + ';');
                outputDeclarationLine += 1;
            }
        }

        var czmFragColor = 'czm_fragColor';
        if (findInSource('gl_FragColor', splitSource)) {
            setAdd(czmFragColor, outputVariables);
            replaceInSourceString('gl_FragColor', czmFragColor, splitSource);
            splitSource.splice(outputDeclarationLine, 0, 'layout(location = 0) out vec4 czm_fragColor;');
            outputDeclarationLine += 1;
        }

        var variableMap = getVariablePreprocessorBranch(outputVariables, splitSource);
        var lineAdds = {};
        for (i = 0; i < splitSource.length; i++) {
            line = splitSource[i];
            for (var variable in variableMap) {
                if (variableMap.hasOwnProperty(variable)) {
                    var matchVar = new RegExp('(layout)[^]+(out)[^]+(' + variable + ')[^]+', 'g');
                    if (matchVar.test(line)) {
                        lineAdds[line] = variable;
                    }
                }
            }
        }

        for (var layoutDeclaration in lineAdds) {
            if (lineAdds.hasOwnProperty(layoutDeclaration)) {
                var variableName = lineAdds[layoutDeclaration];
                var lineNumber = splitSource.indexOf(layoutDeclaration);
                var entry = variableMap[variableName];
                var depth = entry.length;
                var d;
                for (d = 0; d < depth; d++) {
                    splitSource.splice(lineNumber, 0, entry[d]);
                }
                lineNumber += depth + 1;
                for (d = depth - 1; d >= 0; d--) {
                    splitSource.splice(lineNumber, 0, '#endif //' + entry[d]);
                }
            }
        }

        var versionThree = '#version 300 es';
        var foundVersion = false;
        for (i = 0; i < splitSource.length; i++) {
            if (/#version/.test(splitSource[i])) {
                splitSource[i] = versionThree;
                foundVersion = true;
            }
        }

        if (!foundVersion) {
            splitSource.splice(0, 0, versionThree);
        }

        removeExtension('EXT_draw_buffers', splitSource);
        removeExtension('EXT_frag_depth', splitSource);

        replaceInSourceString('texture2D', 'texture', splitSource);
        replaceInSourceString('texture3D', 'texture', splitSource);
        replaceInSourceString('textureCube', 'texture', splitSource);
        replaceInSourceString('gl_FragDepthEXT', 'gl_FragDepth', splitSource);

        if (isFragmentShader) {
            replaceInSourceString('varying', 'in', splitSource);
        } else {
            replaceInSourceString('attribute', 'in', splitSource);
            replaceInSourceString('varying', 'out', splitSource);
        }

        return compileSource(splitSource);
    }

    // Note that this fails if your string looks like
    // searchString[singleCharacter]searchString
    function replaceInSourceString(str, replacement, splitSource) {
        var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])';
        var regex = new RegExp(regexStr, 'g');

        var splitSourceLength = splitSource.length;
        for (var i = 0; i < splitSourceLength; ++i) {
            var line = splitSource[i];
            splitSource[i] = line.replace(regex, '$1' + replacement + '$3');
        }
    }

    function replaceInSourceRegex(regex, replacement, splitSource) {
        var splitSourceLength = splitSource.length;
        for (var i = 0; i < splitSourceLength; ++i) {
            var line = splitSource[i];
            splitSource[i] = line.replace(regex, replacement);
        }
    }

    function findInSource(str, splitSource) {
        var regexStr = '(^|[^\\w])(' + str + ')($|[^\\w])';
        var regex = new RegExp(regexStr, 'g');

        var splitSourceLength = splitSource.length;
        for (var i = 0; i < splitSourceLength; ++i) {
            var line = splitSource[i];
            if (regex.test(line)) {
                return true;
            }
        }
        return false;
    }

    function compileSource(splitSource) {
        var wholeSource = '';

        var splitSourceLength = splitSource.length;
        for (var i = 0; i < splitSourceLength; ++i) {
            wholeSource += splitSource[i] + '\n';
        }
        return wholeSource;
    }

    function setAdd(variable, set) {
        if (set.indexOf(variable) === -1) {
            set.push(variable);
        }
    }

    function getVariablePreprocessorBranch(layoutVariables, splitSource) {
        var variableMap = {};

        var numLayoutVariables = layoutVariables.length;

        var stack = [];
        for (var i = 0; i < splitSource.length; ++i) {
            var line = splitSource[i];
            var hasIF = /(#ifdef|#if)/g.test(line);
            var hasELSE = /#else/g.test(line);
            var hasENDIF = /#endif/g.test(line);

            if (hasIF) {
                stack.push(line);
            } else if (hasELSE) {
                var top = stack[stack.length - 1];
                var op = top.replace('ifdef', 'ifndef');
                if (/if/g.test(op)) {
                    op = op.replace(/(#if\s+)(\S*)([^]*)/, '$1!($2)$3');
                }
                stack.pop();
                stack.push(op);
            } else if (hasENDIF) {
                stack.pop();
            } else if (!/layout/g.test(line)) {
                for (var varIndex = 0; varIndex < numLayoutVariables; ++varIndex) {
                    var varName = layoutVariables[varIndex];
                    if (line.indexOf(varName) !== -1) {
                        if (!defined(variableMap[varName])) {
                            variableMap[varName] = stack.slice();
                        } else {
                            variableMap[varName] = variableMap[varName].filter(function(x) {
                                return stack.indexOf(x) >= 0;
                            });
                        }
                    }
                }
            }
        }

        return variableMap;
    }

    function removeExtension(name, splitSource) {
        var regex = '#extension\\s+GL_' + name + '\\s+:\\s+[a-zA-Z0-9]+\\s*$';
        replaceInSourceRegex(new RegExp(regex, 'g'), '', splitSource);
    }
export default modernizeShader;
